#ifndef SIMULATION_H
#define SIMULATION_H
/*Project 1 - Simulate a single customer queue : M/M/1/K queue
 * Author : Saurabh V. Pendse
 * Unity ID : svpendse
 * Student ID : 001026185
 */
#include <iostream>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <stdint.h>
#include <time.h>
#define IA 16807
#define IM 2147483647
#define AM (1.0/IM)
#define IQ 127773
#define IR 2836
#define MASK 123459876
using namespace std;

class Simulation {
public:
	long kSeed_arrival;				//seed for the generating the interarrival times
	long kSeed_service;				//seed for the generating the service times
	long kSeed_selector;			//seed for the selector

	struct Customer;
	struct EventNode;

	vector<Customer *> customer_list;	//the list of all customers (arrived + lost)
	vector<EventNode *> event_list;		//the event list

	enum QueueType {
		QUEUE_CPU = 0,
		QUEUE_IO1,
		QUEUE_IO2,
		QUEUE_IO3
	};

	typedef struct CustomerStats {
		QueueType queue_type_;
		double arrival_time_;
		double service_time_;
		double waiting_time_;
		double departure_time_;
		bool lost_;
		CustomerStats(QueueType queue_type, double arrival_time, double service_time,
					  double waiting_time, double departure_time, bool lost) :
					  queue_type_(queue_type), 
					  arrival_time_(arrival_time), service_time_(service_time),
					  waiting_time_(waiting_time), departure_time_(departure_time),
					  lost_(lost) {
		}
	}CustomerStats;

	//Customer structure
	typedef struct Customer {
		uint32_t id_;					//the customer ID
		QueueType queue_type_;			//the type of the queue (web server simulation only)
		deque<Customer *> *queue_;		//pointer to the customer queue
		uint32_t queue_number_;			//the queue coresponding to the customer
		double arrival_time_;			//the arrival time
		double service_time_;			//the service time
        double slowdown_;
		double remaining_service_time_;
		double head_of_queue_time_;
		double waiting_time_;			//the waiting time
		double preemption_time_;		//the time for which the customer is preempted
		double departure_time_;			//the departure time

		//web server simulation data structure
		vector<CustomerStats *> customer_stats_;

		uint32_t customers_after_;		//the number of customers in the system immediately after this customer
		bool readd_; 
		bool lost_, serviced_;			//flags to indicate whether the customer has been served yet, or has 									 
		//been lost
		uint32_t departure_number_;
		Customer *dependent_;
		//constructor to initialize a Customer
		Customer(uint32_t id, double arrival_time, double service_time,
				double waiting_time, double departure_time,
				bool serviced, bool lost) : id_(id), arrival_time_(arrival_time),
		service_time_(service_time), waiting_time_(waiting_time), departure_time_(departure_time),
		serviced_(serviced), lost_(lost) {
			dependent_ = NULL;
			readd_ = false;
			customers_after_ = 0;
			departure_number_ = 0;
			preemption_time_ = 0;
			head_of_queue_time_ = 0;
		}
		//utility function to print the customer information
		void print() {
			cout << "Id : " << id_ << ", Arrival time = " << arrival_time_
		 		 << ", Service time = " << service_time_
				 << ", Waiting time = " << waiting_time_
				 << ", Departure time = " << departure_time_
				 << ", Lost = " << lost_
				 << ", serviced = " << serviced_ 
				 << ", customers after = " << customers_after_ 
				 << ", departure number = " << departure_number_ << endl;
		}
	}Customer;

	typedef struct EventNode {
		char type_;						//event type ('A', 'D' or 'L')
		Customer *customer_;			//pointer to the corresponding customer
		double time_;					//the time of the event
		//constructor to initialize an event
		EventNode(char type, Customer *customer, double time) : type_(type),
		customer_(customer), time_(time) {
		}
		//utility function to print the event information
		void print() {
			if (customer_ != NULL) {
				cout << "Type : " << type_ << ", id : " << customer_->id_
					 << ", Queue : " << customer_->queue_type_
					 << ", time : " << time_ << endl; //", queue size = " << customer_queue.size() << endl;
			}
		}

	}EventNode;

	double clr;
	double queue_clr[4];
	double queue_avg_service_time[4], queue_avg_waiting_time[4];
	double visits[4];
	double avg_service_time, avg_waiting_time, avg_system_time;
	double lambda, mu;					//parameters for the interarrival and service time exp. dist.
	double kcpu, kio;					//the cpu and i/o queue capacities
	uint32_t c, l, m;					//k = queue capacity, c = customers to be served, l = parameter, m = the service discipline
	double Ta, Ts;						//The interarrival and service times
	int n;								//number of customers in the queue
	double t1, t2, te;					//times to run the simulation
	uint32_t arrival_count, departure_count, total_arrival_count, total_departure_count;		//counters
	uint32_t serve_count;				//the number of customers served
	double master_clock;				//the master clock
	double sim_time;					//the simulation time

	Simulation() {
		avg_service_time = 0;
		avg_waiting_time = 0;
		avg_system_time = 0;	
	}
	Simulation(int argc, char **argv);

	//function to generate the random number in the range (0, 1), given a seed
	static double ran0(long *idnum);

	//function to generate an exponentially distributed random number with mean 1
	static double expdev(long *idnum);

	//function to generate an exponentially distributed random number with mean "mean"
	static double expntl(double mean, long &seed);

	//finds a particular type of event associated with a customer
	EventNode *find_event_for_customer(Customer *customer, char eventType);

	//checks whether the event list is empty
	bool event_list_empty();

	//returns the current size of the event list
	uint32_t event_list_size();

	//returns the first element (i.e. the next event) from the event list
	EventNode *event_list_front();

	//adds a new element (i.e. a new event) to the event list, ordered by the time of the event
	void event_list_add(EventNode *eventNode);

	//removes an element (i.e. the next event) from the event list
	EventNode *event_list_remove_front();

	//removes a node from the event list
	EventNode *event_list_remove(EventNode *eventNode);

	//prints the contents of the event list
	void event_list_print();

	//prints out the customer diagnostic information
	void customer_diagnostics(uint32_t index);

	//run the simulation until c customers are served
	void run();
	
	//calculates the statistics for the simulation
	virtual void calculate_statistics(); 

	//schedule a departure event for a given customer
	virtual void schedule_departure(Customer *customer) = 0;

	//process an arrival event
	virtual void process_arrival(EventNode *next_event) = 0;

	//process the lost event
	virtual void process_lost(EventNode *next_event) = 0;

	//process the departure event
	virtual void process_departure(EventNode *next_event) = 0;

	virtual void print_queues() {}	
};

class FCFSSimulation : public Simulation {
	deque<Customer *> customer_queue;	//the customer queue
public:
	FCFSSimulation(int argc, char **argv);

	//schedule a departure event for a given customer
	void schedule_departure(Customer *customer);

	//process an arrival event
	void process_arrival(EventNode *next_event);

	//process the lost event
	void process_lost(EventNode *next_event);

	//process the departure event
	void process_departure(EventNode *next_event);
};

class LCFSSimulation : public Simulation {
	deque<Customer *> customer_queue;	//the customer queue
public:
	LCFSSimulation(int argc, char **argv);

	//schedule a departure event for a given customer
	void schedule_departure(Customer *customer);

	//process an arrival event
	void process_arrival(EventNode *next_event);

	//process the lost event
	void process_lost(EventNode *next_event);

	//process the departure event
	void process_departure(EventNode *next_event);
};

class SJFSimulation : public Simulation {
	class CompareCustomer {
	public:
		bool operator()(Customer *c1, Customer *c2) {
			return c1->service_time_ > c2->service_time_;
		}
	};
	//deque<Customer *> customer_queue;
	//vector<Customer *> customer_queue;
	priority_queue<Customer *, vector<Customer *>, CompareCustomer> customer_queue;
public:
	SJFSimulation(int argc, char **argv);

	//schedule a departure event for a given customer
	void schedule_departure(Customer *customer);

	//process an arrival event
	void process_arrival(EventNode *next_event);

	//process the lost event
	void process_lost(EventNode *next_event);

	//process the departure event
	void process_departure(EventNode *next_event);

	void print_queue();
};

class PriorityNPSimulation : public Simulation {
	
	deque<Customer *> customer_queue_1;
	deque<Customer *> customer_queue_2;
	deque<Customer *> customer_queue_3;
	deque<Customer *> customer_queue_4;

	uint32_t capacity[4];		

public:
	PriorityNPSimulation(int argc, char **argv);

	//schedule a departure event for a given customer
	void schedule_departure(Customer *customer);

	//process an arrival event
	void process_arrival(EventNode *next_event);

	//process the lost event
	void process_lost(EventNode *next_event);

	//process the departure event
	void process_departure(EventNode *next_event);

	void init_customer_queue(Customer *customer);

	void calculate_statistics();
	
	void print_queues();
};

class PriorityPSimulation : public Simulation {

	Customer *current_served_;
	deque<Customer *> customer_queue_1;
	deque<Customer *> customer_queue_2;
	deque<Customer *> customer_queue_3;
	deque<Customer *> customer_queue_4;

	uint32_t capacity[4];		
public:
	PriorityPSimulation(int argc, char **argv);

	//schedule a departure event for a given customer
	void schedule_departure(Customer *customer);

	//process an arrival event
	void process_arrival(EventNode *next_event);

	//process the lost event
	void process_lost(EventNode *next_event);

	//process the departure event
	void process_departure(EventNode *next_event);

	void init_customer_queue(Customer *customer);

	void calculate_statistics();

	void print_queues();
};

class WebServerSimulation : public Simulation {

	typedef struct QueueStats {
		char type_;
		uint32_t id_;
		double arrival_time_;
		double service_time_;
		double departure_time_;
		double waiting_time_;
		bool lost_;
		QueueStats(char type, uint32_t id, double arrival_time, double service_time,
				   double departure_time, bool lost) : 
				   type_(type), id_(id), arrival_time_(arrival_time),
				   service_time_(service_time), departure_time_(departure_time),
				   lost_(lost) {
			waiting_time_ = departure_time_ - (arrival_time_ + service_time_);
		}

		void print() {
			cout << "Id = " << id_
				 << "Arrival time = " << arrival_time_
				 << "Service time = " << service_time_ 
				 << "Departure time = " << departure_time_ 
				 << "Waiting time = " << waiting_time_ << endl;
		}
	}QueueStats;

	deque<Customer *> customer_queue;
	deque<Customer *> io_queue_1;
	deque<Customer *> io_queue_2;
	deque<Customer *> io_queue_3;

	vector<QueueStats *> cpu_stats;
	vector<QueueStats *> io_1_stats;
	vector<QueueStats *> io_2_stats;
	vector<QueueStats *> io_3_stats;

	uint32_t cpu_arrivals, cpu_served;
	uint32_t io_1_arrivals, io_1_served;
	uint32_t io_2_arrivals, io_2_served;
	uint32_t io_3_arrivals, io_3_served;

	double mu_cpu;
	double  mu_io[3];
	uint32_t capacity[3];
	long kSeed_service_io[3];			
public:
	WebServerSimulation(int argc, char **argv);

	//schedule a departure event for a given customer
	void schedule_departure(Customer *customer);

	//process an arrival event
	void process_arrival(EventNode *next_event);

	//process the lost event
	void process_lost(EventNode *next_event);

	//process the departure event
	void process_departure(EventNode *next_event);

	void calculate_statistics();

	void print_queues();
};

#endif
